import concurrent.futures
import queue
import threading

q = queue.Queue()


def worker():
    while True:
        item = q.get()
        if item is None:  # Detect the stop signal
            break
        print(f"Working on {item}")
        print(f"Finished {item}")
        q.task_done()


def producer(items):
    for item in items:
        q.put(item)
    print("All task requests sent\n", end="")


# Number of workers and producers
num_workers = 5
num_producers = 3
items_per_producer = 10

# Start workers
workers = []
for _ in range(num_workers):
    w = threading.Thread(target=worker)
    w.start()
    workers.append(w)

# Start producers
with concurrent.futures.ThreadPoolExecutor(max_workers=num_producers) as executor:
    futures = [
        executor.submit(
            producer, range(i * items_per_producer, (i + 1) * items_per_producer)
        )
        for i in range(num_producers)
    ]
    concurrent.futures.wait(futures)

# Block until all tasks are done
q.join()

# Send stop signals to workers
for _ in range(num_workers):
    q.put(None)

# Wait for all threads to finish
for wer in workers:
    wer.join()

print("All work completed")
